/**
 * BibSonomy-Model - Java- and JAXB-Model.
 *
 * Copyright (C) 2006 - 2016 Knowledge & Data Engineering Group,
 *                               University of Kassel, Germany
 *                               http://www.kde.cs.uni-kassel.de/
 *                           Data Mining and Information Retrieval Group,
 *                               University of Würzburg, Germany
 *                               http://www.is.informatik.uni-wuerzburg.de/en/dmir/
 *                           L3S Research Center,
 *                               Leibniz University Hannover, Germany
 *                               http://www.l3s.de/
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.bibsonomy.services;

import static org.bibsonomy.util.ValidationUtils.present;

import java.net.MalformedURLException;
import java.net.URL;

import org.bibsonomy.common.enums.HashID;
import org.bibsonomy.common.enums.SearchType;
import org.bibsonomy.common.exceptions.UnsupportedFormatException;
import org.bibsonomy.common.exceptions.UnsupportedResourceTypeException;
import org.bibsonomy.model.Author;
import org.bibsonomy.model.BibTex;
import org.bibsonomy.model.Bookmark;
import org.bibsonomy.model.GoldStandardBookmark;
import org.bibsonomy.model.GoldStandardPublication;
import org.bibsonomy.model.Person;
import org.bibsonomy.model.PersonName;
import org.bibsonomy.model.Post;
import org.bibsonomy.model.Resource;
import org.bibsonomy.model.User;
import org.bibsonomy.model.enums.FavouriteLayoutSource;
import org.bibsonomy.model.enums.Order;
import org.bibsonomy.model.enums.PersonResourceRelationType;
import org.bibsonomy.model.enums.SimpleExportLayout;
import org.bibsonomy.model.factories.ResourceFactory;
import org.bibsonomy.model.user.settings.FavouriteLayout;
import org.bibsonomy.model.util.BibTexUtils;
import org.bibsonomy.services.export.CSLUtils;
import org.bibsonomy.util.StringUtils;
import org.bibsonomy.util.UrlBuilder;
import org.bibsonomy.util.UrlUtils;

/**
 * FIXME: introduce a factory for the url generator and remove all *AndSysUrl methods
 * 
 * TODO: Unify URL constructions for various cases (history, community posts,
 * regular posts) Generates the URLs used by the web application.
 * 
 * @author rja
 */
public class URLGenerator {

	/**
	 * Provides page names.
	 * 
	 * XXX: experimental!
	 * 
	 * @author rja
	 * 
	 */
	public enum Page {
		/**
		 * all posts users' have sent me using the "send:" system tag
		 */
		INBOX("inbox"),
		/**
		 * all posts I have picked
		 */
		CLIPBOARD("clipboard");

		private final String path;

		private Page(final String path) {
			this.path = path;
		}

		/**
		 * @return The string representation of this page
		 */
		public String getPath() {
			return this.path;
		}
	}

	private static final String BOOKMARK = Bookmark.class.getSimpleName();

	private static final String ADMIN_PREFIX = "admin";
	private static final String AUTHOR_PREFIX = "author";
	private static final String BIBTEXEXPORT_PREFIX = "bib";
	private static final String BIBTEXKEY_PREFIX = "bibtexkey";
	public  static final String BOOKMARK_PREFIX = "url";
	private static final String CONCEPTS_PREFIX = "concepts";
	private static final String CONCEPT_PREFIX = "concept";
	private static final String DOCUMENT_PREFIX = "documents";
	private static final String DISAMBIGUATION_PREFIX = "person";
	private static final String FOLLOWERS_PREFIX = "followers";
	private static final String FRIEND_PREFIX = "friend";
	private static final String GROUPS = "groups";
	private static final String GROUP_PREFIX = "group";
	private static final String LOGIN_PREFIX = "login";
	private static final String LAYOUT_PREFIX = "layout";
	private static final String ENDNOTE_PREFIX = "endnote";
	private static final String MSWORD_PREFIX = "msofficexml";
	private static final String REGISTER = "register";
	private static final String MYBIBTEX_PREFIX = "myBibTex";
	private static final String MYDOCUMENTS_PREFIX = "myDocuments";
	private static final String MYDUPLICATES_PREFIX = "myDuplicates";
	private static final String MYHOME_PREFIX = "myHome";
	private static final String MYRELATIONS_PREFIX = "myRelations";
	private static final String MYSEARCH_PREFIX = "mySearch";
	private static final String PICTURE_PREFIX = "picture";
	private static final String PERSON_PREFIX = "person";
	private static final String PUBLICATION_PREFIX = "bibtex";
	private static final String RELEVANTFOR_PREFIX = "relevantfor";
	private static final String SEARCH_PREFIX = "search";
	private static final String SETTINGS_PREFIX = "settings";
	private static final String TAG_PREFIX = "tag";
	private static final String USER_PREFIX = "user";
	private static final String VIEWABLE_PREFIX = "viewable";
	private static final String VIEWABLE_FRIENDS_SUFFIX = "friends";
	private static final String VIEWABLE_PRIVATE_SUFFIX = "private";
	private static final String VIEWABLE_PUBLIC_SUFFIX = "public";
	private static final String HISTORY_PREFIX = "history";
	private static final String USER_RELATION = "handleUserRelation";

	private static final String PUBLICATION_INTRA_HASH_ID = String.valueOf(HashID.INTRA_HASH.getId());
	private static final String PUBLICATION_INTER_HASH_ID = String.valueOf(HashID.INTER_HASH.getId());

	private static final String PERSON_INTRO = "persons";

	private static final String POST_PUBLICATION = "postPublication";
	
	private static final String DISCUSSION_ID = "#discussion-section";

	/**
	 * The default gives relative URLs.
	 */
	private String projectHome = "/";

	/**
	 * Per default, generated URLs are not checked.
	 */
	private boolean checkUrls = false;

	/**
	 * Sets up a new URLGenerator with the default projectHome ("/") and no
	 * checking of URLs.
	 */
	public URLGenerator() {
		// noop
	}

	/**
	 * Sets up a new URLGenerator with the given projectHome.
	 * 
	 * @param projectHome
	 */
	public URLGenerator(final String projectHome) {
		super();
		this.projectHome = projectHome;
	}

	/**
	 * Creates an absolute URL for the given path.
	 * 
	 * @param path
	 *            - the path part of the URL (TODO: with or without leading
	 *            "/"?)
	 * @return The absolute URL.
	 */
	public String getAbsoluteUrl(final String path) {
		return this.getUrl(this.projectHome + path);
	}

	/**
	 * Constructs a url to the admin page if no name is given or a url to a
	 * subpage otherwise
	 * 
	 * @param name
	 * @return The URL pointing to the page.
	 */
	public String getAdminUrlByName(final String name) {
		String url = this.projectHome + ADMIN_PREFIX;
		if (present(name)) {
			url += "/" + UrlUtils.encodePathSegment(name);
		}
		return this.getUrl(url);
	}

	/**
	 * Constructs the URL for the author's page.
	 * 
	 * @param name
	 *            the name of the author
	 * @return The URL for the author's page.
	 */
	public String getAuthorUrlByPersonName(final PersonName name) {
		final String url = this.projectHome
				+ AUTHOR_PREFIX
				+ "/"
				+ UrlUtils.encodePathSegment(name.getFirstName() + " "
						+ name.getLastName());
		return this.getUrl(url);
	}

	/**
	 * Constructs the URL for the author's page.
	 * 
	 * @param author
	 *            the name of the author
	 * @return The URL for the author's page.
	 */
	public String getAuthorUrlByAuthor(final Author author) {
		final String url = this.projectHome
				+ AUTHOR_PREFIX
				+ "/"
				+ UrlUtils.encodePathSegment(author.getFirstName() + " "
						+ author.getLastName());
		return this.getUrl(url);
	}

	/**
	 * Constructs the URL for the author's page.
	 * 
	 * @param authorLastName
	 * @return The URL for the author's page.
	 */
	public String getAuthorUrlByName(final String authorLastName) {
		final String url = this.projectHome + AUTHOR_PREFIX + "/" + UrlUtils.encodePathSegment(BibTexUtils.cleanBibTex(authorLastName));
		return this.getUrl(url);
	}

	/**
	 * Constructs a URL for the clipboard page, i.e. /clipboard
	 * 
	 * @return URL pointing to the clipboard page.
	 */
	public String getClipboardUrl() {
		final String url = this.projectHome + Page.CLIPBOARD.getPath();
		return this.getUrl(url);
	}

	/**
	 * @param bookmark
	 * @return the bookmark url
	 */
	public String getBookmarkUrl(final Bookmark bookmark) {
		return this.getBookmarkUrl(bookmark, (Post<? extends Resource>) null);
	}

	/**
	 * Constructs a URL for the given resource and user. If no user is given,
	 * the URL points to all posts for that resource.
	 * 
	 * @param bookmark
	 *            - must have proper inter and intra hashes (a call to
	 *            {@link Resource#recalculateHashes()} might be necessary but is
	 *            not done by this method)
	 * 
	 * @param user
	 *            - if null, the URL to all posts for the given bookmark is
	 *            returned.
	 * @return - The URL which represents the given bookmark
	 */
	public String getBookmarkUrl(final Bookmark bookmark, final User user) {
		/*
		 * no user given
		 */
		if (!present(user) || !present(user.getName())) {
			return this.getUrl(this.projectHome + BOOKMARK_PREFIX + "/" + bookmark.getInterHash());
		}
		return this.getBookmarkUrlByIntraHashAndUsername(bookmark.getIntraHash(), user.getName());
	}

	/**
	 * Constructs a bookmark URL for the given intraHash. If you have the
	 * resource as object, please use {@link #getBookmarkUrl(Bookmark, User)}
	 * @param bookmark 
	 * @param post 
	 * @return The URL pointing to the post of that user for the bookmark
	 *         represented by the given intrahash.
	 */
	public String getBookmarkUrl(final Bookmark bookmark, final Post<? extends Resource> post) {
		final UrlBuilder builder = new UrlBuilder(this.projectHome);
		builder.addPathElement(BOOKMARK_PREFIX);
		builder.addPathElement(bookmark.getInterHash());
		addParamsForCommunityPage(bookmark, post, builder);
		return this.getUrl(builder.asString());
	}

	/**
	 * Constructs a bookmark URL for the given intraHash and userName. If you
	 * have the resource as object, please use
	 * {@link #getBookmarkUrl(Bookmark, User)}
	 * 
	 * @param intraHash
	 * @param userName
	 * @return The URL pointing to the post of that user for the bookmark
	 *         represented by the given intrahash.
	 */
	public String getBookmarkUrlByIntraHashAndUsername(final String intraHash, final String userName) {
		String url = this.projectHome + BOOKMARK_PREFIX + "/" + intraHash;
		if (present(userName)) {
			url += "/" + UrlUtils.encodePathSegment(userName);
		}
		return this.getUrl(url);
	}

	/**
	 * url for BibTex export
	 * 
	 * @param intraHash
	 * @param userName
	 * @return returns the BibTex Export url
	 */
	@Deprecated // see getMSWordUrlByIntraHashAndUserName
	public String getBibtexExportUrlByIntraHashAndUserName(final String intraHash, final String userName) {
		return getLayoutUrl(intraHash, userName, BIBTEXEXPORT_PREFIX);
	}
	
	/**
	 * url for Endnote export
	 * 
	 * @param intraHash
	 * @param userName
	 * @return returns the Endnote export url
	 */
	@Deprecated // FIXME: see getMSWordUrlByIntraHashAndUserName
	public String getEndnoteUrlByIntraHashAndUserName(final String intraHash, final String userName){
		return getLayoutUrl(intraHash, userName, ENDNOTE_PREFIX);
	}
	
	/**
	 * url for MS WORD Reference Manager
	 * 
	 * @param intraHash
	 * @param userName
	 * @return returns the MS WORD Reference Manager url
	 */
	@Deprecated // FIXME: a more generic method getExportUrlForPost()
	public String getMSWordUrlByIntraHashAndUserName(final String intraHash, final String userName){
		return getLayoutUrl(intraHash, userName, MSWORD_PREFIX);
	}

	/**
	 * @param intraHash
	 * @param userName
	 * @return
	 */
	private String getLayoutUrl(final String intraHash, final String userName, final String layout) {
		String url = this.projectHome + LAYOUT_PREFIX + "/" + layout + "/" + PUBLICATION_PREFIX + "/" + PUBLICATION_INTRA_HASH_ID + intraHash;
		if (present(userName)) {
			url += "/" + UrlUtils.encodePathSegment(userName);
		}
		return this.getUrl(url);
	}

	/**
	 * Constructs a concepts URL for the given name.
	 * 
	 * @param name
	 * @return The URL pointing to the concepts of the user.
	 */
	public String getConceptsUrlByString(final String name) {
		String url = this.projectHome + CONCEPTS_PREFIX;
		if (present(name)) {
			url += "/" + UrlUtils.encodePathSegment(name);
		}
		return this.getUrl(url);
	}

	/**
	 * Constructs a concepts URL for the given user i.e. a URL of the form
	 * /concepts/USERNAME
	 * 
	 * @param user
	 * @return The URL pointing to the concepts of the user
	 */
	public String getConceptsUrlForUser(final User user) {
		return this.getConceptsUrlByString(user.getName());
	}

	/**
	 * Constructs a concept URL for the given username and tagname, i.e. a URL
	 * of the form /concept/user/USERNAME/TAGNAME.
	 * 
	 * @param userName
	 * @param tagName
	 * @return The URL pointing to the concepts of the user with the specified
	 *         tags.
	 */
	public String getConceptUrlByUserNameAndTagName(final String userName, final String tagName) {
		String url = this.projectHome + CONCEPT_PREFIX + "/" + USER_PREFIX;
		url += "/" + UrlUtils.encodePathSegment(userName);
		url += "/" + UrlUtils.encodePathSegment(tagName);

		return this.getUrl(url);
	}
	
	/**
	 * @param post
	 * @return edit url for the post
	 */
	public String getEditUrlOfPost(final Post<? extends Resource> post) {
		final UrlBuilder urlBuilder = new UrlBuilder(this.projectHome);
		final Resource resource = post.getResource();
		urlBuilder.addPathElement(getEditUrlByResourceClass(resource.getClass()));
		final String hash;
		if (ResourceFactory.isCommunityResource(resource)) {
			hash = resource.getInterHash();
		} else {
			hash = resource.getIntraHash();
		}
		urlBuilder.addParameter("intraHashToUpdate", hash);
		return this.getUrl(urlBuilder.asString());
	}
	
	/**
	 * @param post
	 * @param ckey 
	 * @return the delete url of the post
	 */
	public String getDeleteUrlOfPost(final Post<? extends Resource> post, final String ckey) {
		final UrlBuilder urlBuilder = new UrlBuilder(this.projectHome);
		urlBuilder.addPathElement("deletePost");
		
		final Resource resource = post.getResource();
		if (ResourceFactory.isCommunityResource(resource)) {
			urlBuilder.addParameter("resourceHash", resource.getInterHash());
		} else {
			urlBuilder.addParameter("resourceHash", resource.getIntraHash());
			urlBuilder.addParameter("owner", post.getUser().getName());
		}
		urlBuilder.addParameter("ckey", ckey);
		
		return this.getUrl(urlBuilder.asString());
	}
	
	/**
	 * @param post
	 * @return the copy url of the post for logged in user
	 */
	public String getCopyUrlOfPost(final Post<? extends Resource> post) {
		return getCopyUrlOfPost(post, true, false);
	}
	/**
	 * @param post
	 * @param useSuperiorResourceClass 
	 * @param forceCommunityResource 
	 * @return the copy url for the community post
	 */
	public String getCopyUrlOfPost(final Post<? extends Resource> post, boolean useSuperiorResourceClass, boolean forceCommunityResource) {
		final UrlBuilder urlBuilder = new UrlBuilder(this.projectHome);
		final Resource resource = post.getResource();
		Class<? extends Resource> resourceClass = resource.getClass();
		if (useSuperiorResourceClass) {
			resourceClass = ResourceFactory.findSuperiorResourceClass(resource);
		}
		
		if (forceCommunityResource) {
			resourceClass = ResourceFactory.findCommunityResourceClass(resource);
		}
		
		urlBuilder.addPathElement(getEditUrlByResourceClass(resourceClass));
		if (ResourceFactory.isCommunityResource(resource)) {
			urlBuilder.addParameter("hash", resource.getInterHash());
		} else {
			urlBuilder.addParameter("hash", resource.getIntraHash());
			urlBuilder.addParameter("user", post.getUser().getName());
			if (forceCommunityResource) {
				urlBuilder.addParameter("editBeforeSaving", String.valueOf(true));
			}
		}
		
		return this.getUrl(urlBuilder.asString());
	}

	/**
	 * @param resourceClass
	 * @return
	 */
	private static String getEditUrlByResourceClass(final Class<? extends Resource> resourceClass) {
		return "edit" + StringUtils.capitalizeWord(getResourceNameForEditForm(resourceClass));
	}
	
	/**
	 * @param resourceClass
	 * @return
	 */
	private static String getResourceNameForEditForm(Class<? extends Resource> resourceClass) {
		// XXX: special handling for the publication class; remove after renaming the class
		if (BibTex.class.equals(resourceClass)) {
			return ResourceFactory.PUBLICATION_CLASS_NAME;
		}
		return resourceClass.getSimpleName();
	}

	/**
	 * url of the document
	 * 
	 * @param intraHash
	 * @param userName
	 * @param fileName
	 * @return returns the url of the document
	 */
	public String getDocumentUrlByIntraHashUserNameAndFileName(final String intraHash, final String userName, final String fileName){
		String url = this.projectHome + DOCUMENT_PREFIX + "/" + intraHash;
		if (present(userName)) {
			url += "/" + UrlUtils.encodePathSegment(userName);
		}
		url += "/" + UrlUtils.encodePathSegment(fileName);
		
		return this.getUrl(url);
	}

	/**
	 * Constructs a URL with the posts of all users you are following, i.e.
	 * /followers
	 * 
	 * @return URL pointing to the posts of the users you are following.
	 */
	public String getFollowersUrl() {
		final String url = this.projectHome + FOLLOWERS_PREFIX;
		return this.getUrl(url);
	}

	/**
	 * Constructs a friend URL for the given username, i.e. /friend/USERNAME
	 * 
	 * @param userName
	 * @return URL pointing to the posts viewable for friends of User with name
	 *         username.
	 */
	public String getFriendUrlByUserName(final String userName) {
		String url = this.projectHome + FRIEND_PREFIX + "/";
		url += UrlUtils.encodePathSegment(userName);
		return this.getUrl(url);
	}

	/**
	 * Constructs a friend URL for the given username and tagname, i.e.
	 * /friend/USERNAME/TAGNAME
	 * 
	 * @param userName
	 * @param tagName
	 * @return URL pointing to the posts viewable for friends of User with name
	 *         username and tag tagName.
	 */
	public String getFriendUrlByUserNameAndTagName(final String userName, final String tagName) {
		final String url = this.getFriendUrlByUserName(userName) + "/" + UrlUtils.encodePathSegment(tagName);
		return this.getUrl(url);
	}

	private String getPartialPostUrlByInterHashAndUserName(final String hash, final String userName, boolean bookmark) {
		String urlPart = (bookmark ? BOOKMARK_PREFIX : PUBLICATION_PREFIX)
				+ "/" + hash;
		
		if (present(userName)) {
			return this.getUrl(urlPart + "/" + UrlUtils.encodePathSegment(userName));
		}
		
		return urlPart;
	}

	/**
	 * The URL to the history of a post
	 * 
	 * @param hash
	 * @param userName
	 * @param resourceType TODO: should not be string
	 * @return the url for the history page
	 */
	public String getHistoryURLByHashAndUserName(final String hash, final String userName, String resourceType) {
		return this.getUrl(this.projectHome + HISTORY_PREFIX + "/" + getPartialPostUrlByInterHashAndUserName(hash, userName, BOOKMARK.equalsIgnoreCase(resourceType)));
	}
	
	/**
	 * @param post
	 * @return the history url for a community post
	 */
	public String getHistoryUrlForPost(final Post<? extends Resource> post) {
		final Resource resource = post.getResource();
		final Class<? extends Resource> resourceType = resource.getClass();
		final String interHash = resource.getInterHash();
		
		// XXX: not nice
		if (resourceType == GoldStandardPublication.class) {
			return this.getHistoryUrlForCommunityPublication(interHash);
		}
		
		if (resourceType == GoldStandardBookmark.class) {
			return this.getHistoryUrlForCommunityBookmark(interHash);
		}
		
		final String intraHash = resource.getIntraHash();
		final String name = post.getUser().getName();
		if (resourceType == Bookmark.class) {
			return this.getHistoryUrlForBookmark(intraHash, name);
		}
		
		if (resourceType == BibTex.class) {
			return this.getHistoryUrlForPublication(intraHash, name);
		}
		
		throw new UnsupportedResourceTypeException();
	}

	/**
	 * @param intraHash
	 * @param userName
	 * @return
	 */
	private String getHistoryUrlForBookmark(String intraHash, String userName) {
		return this.getUrl(this.projectHome + HISTORY_PREFIX + "/" + BOOKMARK_PREFIX + "/" + intraHash + "/" + userName);
	}
	
	/**
	 * 
	 * @param intraHash
	 * @param userName
	 * @return
	 */
	private String getHistoryUrlForPublication(String intraHash, String userName) {
		return this.getUrl(this.projectHome + HISTORY_PREFIX + "/" + PUBLICATION_PREFIX + "/" + intraHash + "/" + userName);
	}

	/**
	 * @param hash
	 * @return
	 */
	private String getHistoryUrlForCommunityBookmark(final String hash) {
		return this.getUrl(this.projectHome + HISTORY_PREFIX + "/" + BOOKMARK_PREFIX + "/" + hash);
	}

	/**
	 * @param hash
	 * @return
	 */
	private String getHistoryUrlForCommunityPublication(final String hash) {
		return this.getUrl(this.projectHome + HISTORY_PREFIX + "/" + PUBLICATION_PREFIX + "/" + hash);
	}

	/**
	 * Constructs the URL for the groups page
	 * 
	 * @return URL pointing to the groups page
	 */
	public String getGroupsUrl() {
		final String url = this.projectHome + GROUPS;
		return this.getUrl(url);
	}

	/**
	 * Constructs the URL for the group's page.
	 * 
	 * @param groupName
	 * @return The URL for the group's page.
	 */
	public String getGroupUrlByGroupName(final String groupName) {
		final String url = this.getGroupUrlString(groupName);
		return this.getUrl(url);
	}

	/**
	 * @param groupName
	 * @return
	 */
	private String getGroupUrlString(final String groupName) {
		return this.projectHome + GROUP_PREFIX + "/" + UrlUtils.encodePathSegment(groupName);
	}
	
	/**
	 * build group settings path
	 * @param groupName
	 * @param selectedTab
	 * @return the group settings url for the specified group
	 */
	public String getGroupSettingsUrlByGroupName(final String groupName, Integer selectedTab) {
		String url = this.projectHome + "settings" + "/" + GROUP_PREFIX + "/" + UrlUtils.encodePathSegment(groupName);
		if (present(selectedTab)) {
			url += "?selTab=" + selectedTab.intValue();
		}
		return this.getUrl(url);
	}

	/**
	 * Constructs the URL for the group's page for all posts tagged with tagName
	 * 
	 * @param groupName
	 * @param tagName
	 * @return URL pointing to the site of the group with all posts tagged with
	 *         tagName
	 */
	public String getGroupUrlByGroupNameAndTagName(final String groupName, final String tagName) {
		final String url = this.getGroupUrlString(groupName) + "/" + UrlUtils.encodePathSegment(tagName);
		return this.getUrl(url);
	}

	/**
	 * Constructs the URL for the login page
	 * 
	 * @return URL pointing to the login page
	 */
	public String getLoginUrl() {
		String url = this.projectHome + LOGIN_PREFIX;
		return this.getUrl(url);
	}

	/**
	 * Constructs the URL for the register page
	 * 
	 * @return URL pointing to the register page
	 */
	public String getRegisterUrl() {
		final String url = this.projectHome + REGISTER;
		return this.getUrl(url);
	}

	/**
	 * Constructs a URL pointing to the bibtex-bookmarks and publications of the
	 * user, i.e. /myBibTex
	 * 
	 * @return URL pointing to the bookmarks and publications of the user
	 */
	public String getMyBibTexUrl() {
		String url = this.projectHome + MYBIBTEX_PREFIX;
		return this.getUrl(url);
	}

	/**
	 * Constructs a URL pointing to the documents of the user i.e. /myDocuments
	 * 
	 * @return URL pointing to the documents of the user
	 */
	public String getMyDocumentsUrl() {
		String url = this.projectHome + MYDOCUMENTS_PREFIX;
		return this.getUrl(url);
	}

	/**
	 * Constructs a URL pointing to the duplicates of the user i.e.
	 * /myDuplicates
	 * 
	 * @return URL pointing to the duplicates of the user
	 */
	public String getMyDuplicatesUrl() {
		String url = this.projectHome + MYDUPLICATES_PREFIX;
		return this.getUrl(url);
	}

	/**
	 * Constructs a URL pointing to the bookmarks and publications of the user,
	 * i.e. /myHome
	 * 
	 * @return URL pointing to the bookmarks and publications of the user
	 */
	public String getMyHomeUrl() {
		String url = this.projectHome + MYHOME_PREFIX;
		return this.getUrl(url);
	}

	/**
	 * Constructs a URL pointing to the relations of the user i.e. /myRelations
	 * 
	 * @return URL pointing to the relations of the user
	 */
	public String getMyRelationsUrl() {
		String url = this.projectHome + MYRELATIONS_PREFIX;
		return this.getUrl(url);
	}

	/**
	 * Constructs a URL pointing to the fast user search, i.e. /mySearch
	 * 
	 * @return URL pointing to the user search
	 */
	public String getMySearchUrl() {
		String url = this.projectHome + MYSEARCH_PREFIX;
		return this.getUrl(url);
	}

	/**
	 * Constructs a URL for the given resource's intrahash. If you have the post
	 * as object, please use {@link #getPostUrl(Post)}.
	 * 
	 * @param type
	 *            - The type of resource. Currently, only URLs for
	 *            {@link Bookmark} or {@link BibTex} are supported.
	 * @param
	 * @param userName
	 * @return The URL pointing to the post of that user for the resource
	 *         represented by the given intrahash.
	 */
	public String getObjectUrl(final Class<?> type, final String id, final String userName) {
		if (type == Person.class) {
			return this.getPersonUrl(id);
		}
		if (type == Bookmark.class) {
			return this.getBookmarkUrlByIntraHashAndUsername(id, userName);
		} else if (type == BibTex.class) {
			return this.getPublicationUrlByIntraHashAndUsername(id, userName);
		} else {
			throw new IllegalArgumentException(type + " not supported");
		}
	}

	/**
	 * Returns the URL which represents a post. Depending on the type of the
	 * resource, this forwarded to {@link #getBookmarkUrl(Bookmark, User)} and
	 * {@link #getPublicationUrl(BibTex, User)}.
	 * 
	 * @param post
	 *            - The post for which the URL should be constructed. User and
	 *            resources must not be null.
	 * @return The URL representing the given post.
	 */
	public String getPostUrl(final Post<? extends Resource> post) {
		final Resource resource = post.getResource();
		if (resource instanceof Bookmark) {
			return this.getBookmarkUrl(((Bookmark) resource), post.getUser());
		} else if (resource instanceof BibTex) {
			return this.getPublicationUrl(((BibTex) resource), post.getUser());
		} else {
			throw new UnsupportedResourceTypeException();
		}
	}
	
	/**
	 * @param post
	 * @param favl
	 * @return url
	 */
	public String getPostExportUrl(final Post<? extends Resource> post, final FavouriteLayout favl) {
		final Resource resource = post.getResource();
		final FavouriteLayoutSource source = favl.getSource();
		final String style = favl.getStyle();
		final User user = post.getUser();
		if (resource instanceof Bookmark) {
			return "/layout/" + style.toLowerCase() + "/" + this.getBookmarkUrl(((Bookmark) resource), user);
		}
		if (resource instanceof BibTex) {
			final BibTex publication = (BibTex) resource;
			final String publicationUrl = this.getPublicationUrl(publication, user);
			switch (source) {
			case CSL:
				final String normedStyle = CSLUtils.normStyle(style);
				return "/csl-layout/" + normedStyle.toUpperCase() + publicationUrl;
			case JABREF:
				return "/layout/" + style.toLowerCase() + publicationUrl;
			case SIMPLE:
				if (SimpleExportLayout.BIBTEX.toString().equals(style)) {
					return "/bib" + this.getPublicationUrl(publication, post.getUser());
				}
				if (SimpleExportLayout.ENDNOTE.toString().equals(style)) {
					return "/endnote" + publicationUrl;
				}
				//$FALL-THROUGH$
			default:
				throw new UnsupportedFormatException(source + "/" + style);
			}
		}
		
		throw new UnsupportedResourceTypeException(resource.getClass() + " not supported");
	}
	
	/**
	 * @param favl
	 * @param intraHash
	 * @param userName
	 * @return returns citation link
	 */
	public String getCitationUrlbyIntraHashUserName(FavouriteLayout favl, final String intraHash, final String userName){
		String url =  "/" + LAYOUT_PREFIX + "/" + favl.getStyle().toLowerCase() + "/" + PUBLICATION_PREFIX + "/" + PUBLICATION_INTRA_HASH_ID + intraHash;
		if (present(userName)) {
			url += "/" + UrlUtils.encodePathSegment(userName);
		}
		return this.getUrl(url);
	}

	/**
	 * @return the projectHome
	 */
	public String getProjectHome() {
		return this.projectHome;
	}

	/**
	 * @return URL to all publications of the main page in bibtex formats.
	 */
	public String getPublicationsAsBibtexUrl() {
		final String url = this.projectHome + BIBTEXEXPORT_PREFIX;
		return this.getUrl(url);
	}

	/**
	 * Constructs a URL to all publications of the publication page of the user
	 * with name userName, i.e. /bib/user/USERNAME
	 * 
	 * @param userName
	 * @return URL pointing to publications in bibtex format of user with name
	 *         userName
	 */
	public String getPublicationsAsBibtexUrlByUserName(final String userName) {
		String url = getPublicationsAsBibtexUrl();
		url += "/" + USER_PREFIX;
		url += "/" + UrlUtils.encodePathSegment(userName);
		return this.getUrl(url);
	}
	
	/**
	 * @param resource
	 * @return the link for the resource
	 */
	public String getResourceUrl(final Resource resource) {
		return getResourceUrl(resource, null);
	}
	
	/**
	 * @param resource
	 * @param post 
	 * @return the link for the resource
	 */
	public String getResourceUrl(final Resource resource, final Post<? extends Resource> post) {
		// XXX: not nice :(
		if (resource instanceof Bookmark) {
			return getBookmarkUrl((Bookmark) resource, post);
		}
		
		if (resource instanceof BibTex) {
			return getPublicationUrl((BibTex) resource, post);
		}
		
		throw new UnsupportedResourceTypeException(resource.getClass().getName() + " not supported");
	}
	
	/**
	 * @param post
	 * @return the resource url
	 */
	public String getResourceUrl(final Post<? extends Resource> post) {
		final Resource resource = post.getResource();
		return getResourceUrl(resource, post);
	}
	
	/**
	 * @param publication
	 * @return the publication url
	 */
	public String getPublicationUrl(final BibTex publication) {
		return this.getPublicationUrl(publication, (Post<? extends Resource>) null);
	}
	
	/**
	 * @param publication
	 * @param post 
	 * @return the interhash url
	 */
	public String getPublicationUrl(final BibTex publication, final Post<? extends Resource> post) {
		final UrlBuilder builder = new UrlBuilder(this.projectHome);
		builder.addPathElement(PUBLICATION_PREFIX);
		// final String title = publication.getTitle(); see issue #2512
		String path = publication.getInterHash();
		/*if (present(title)) {
			path += "_" + StringUtils.replaceNonNumbersOrLetters(StringUtils.foldToASCII(title).trim(), "_");
		}*/
		builder.addPathElement(path);
		addParamsForCommunityPage(publication, post, builder);
		return this.getUrl(builder.asString());
	}

	/**
	 * @param resource
	 * @param post
	 * @param builder
	 */
	private static void addParamsForCommunityPage(final Resource resource, final Post<? extends Resource> post, final UrlBuilder builder) {
		final Integer ratingCount = resource.getNumberOfRatings();
		if (present(ratingCount) && ratingCount.intValue() == 0 && present(post)) {
			final User user = post.getUser();
			final String hash;
			if (present(user)) {
				builder.addParameter("postOwner", user.getName());
				hash = post.getResource().getIntraHash();
			} else {
				hash = post.getResource().getInterHash();
			}
			
			builder.addParameter("intraHash", hash);
		}
	}

	/**
	 * Constructs a URL for the given resource and user. If no user is given,
	 * the URL points to all posts for that resource.
	 * 
	 * @param publication
	 *            - must have proper inter and intra hashes (a call to
	 *            {@link Resource#recalculateHashes()} might be necessary but is
	 *            not done by this method)
	 * 
	 * @param user
	 *            - if null, the URL to all posts for the given publication is
	 *            returned.
	 * @return - The URL which represents the given publication.
	 */
	public String getPublicationUrl(final BibTex publication, final User user) {
		if (!present(user) || !present(user.getName())) {
			/*
			 * If a user name is given, return the url to that users post
			 * (intrahash + username) otherwise return the URL to the resources
			 * page (interhash)
			 */
			String url = this.projectHome + PUBLICATION_PREFIX + "/"
					+ PUBLICATION_INTER_HASH_ID + publication.getInterHash();
			return this.getUrl(url);
		}
		final String url = this.projectHome + PUBLICATION_PREFIX + "/"
				+ PUBLICATION_INTRA_HASH_ID + publication.getIntraHash() + "/"
				+ UrlUtils.encodePathSegment(user.getName());
		return this.getUrl(url);
	}

	/**
	 * Constructs a URL for all the publications with the specified BibTeX key,
	 * i.e. /bibtexkey/BIBTEXKEY
	 * 
	 * @param bibtexKey
	 * @return URL pointing to all publications with BibTeX key bibtexKey
	 */
	public String getPublicationUrlByBibTexKey(final String bibtexKey) {
		String url = this.projectHome + BIBTEXKEY_PREFIX;
		url += "/" + UrlUtils.encodePathSegment(bibtexKey);

		return this.getUrl(url);
	}

	/**
	 * Constructs a URL for all the publications with the specified BibTeX key
	 * and username, i.e. /bibtexkey/BIBTEXKEY/USERNAME
	 * 
	 * @param bibtexKey
	 * @param userName
	 * @return URL pointing to all publications with BibTeX key bibtexKey and
	 *         user name userName
	 */
	public String getPublicationUrlByBibTexKeyAndUserName(final String bibtexKey, final String userName) {
		String url = this.getPublicationUrlByBibTexKey(bibtexKey);
		url += "/" + UrlUtils.encodePathSegment(userName);

		return this.getUrl(url);
	}

	/**
	 * Constructs a URL for a publication specified by its inter hash.
	 * 
	 * @param interHash
	 * @return URL pointing to the publication represented by the inter hash
	 */
	public String getPublicationUrlByInterHash(final String interHash) {
		return this.getPublicationUrlByInterHashAndUsername(interHash, null);
	}
	
	/**
	 * Constructs a URL for a publication specified by its inter hash and the
	 * username. If no username is present, it will not occur in the URL and the
	 * trailing '/' will be omitted.
	 * 
	 * @param interHash
	 * @param userName
	 * @return URL pointing to the publication represented by the interHash and
	 *         the userName
	 */
	public String getPublicationUrlByInterHashAndUsername(final String interHash, final String userName) {
		final String url = this.projectHome + this.PUBLICATION_PREFIX + "/" + PUBLICATION_INTER_HASH_ID + interHash;
		
		if (present(userName)) {
			return this.getUrl(url + "/" + UrlUtils.encodePathSegment(userName));
		}
		
		return this.getUrl(url);
	}

	/**
	 * Constructs a URL for a publication specified by its intra hash.
	 * 
	 * @param intraHash
	 * @return URL pointing to the publication represented by the intra hash
	 */
	public String getPublicationUrlByIntraHash(final String intraHash) {
		return this.getPublicationUrlByIntraHashAndUsername(intraHash, null);
	}

	/**
	 * Constructs a URL for a publication specified by its intra hash and the
	 * username. If no username is present, it will not occur in the URL and the
	 * trailing '/' will be omitted.
	 * 
	 * @param intraHash
	 * @param userName
	 * @return URL pointing to the publication represented by the intraHash and
	 *         the userName
	 */
	public String getPublicationUrlByIntraHashAndUsername(final String intraHash, final String userName) {
		final String url = this.projectHome + PUBLICATION_PREFIX + "/" + PUBLICATION_INTRA_HASH_ID + intraHash;

		if (present(userName)) {
			return this.getUrl(url + "/" + UrlUtils.encodePathSegment(userName));
		}
		return this.getUrl(url);
	}

	/**
	 * Constructs a URL for a publication specified by its post
	 * 
	 * @param post
	 * @return URL pointing to the publication represented by the intraHash and
	 *         the userName
	 */
	public String getPublicationUrlByPost(final Post<BibTex> post) {
		final User user = post.getUser();
		if (present(user)) {
			return this.getPublicationUrlByIntraHashAndUsername(post.getResource().getIntraHash(), user.getName());
		}
		
		// FIXME: use new url
		return this.getPublicationCommunityUrlByInterHash(post.getResource().getInterHash());
	}

	/**
	 * @param interHash
	 * @return the link to the community post
	 */
	public String getPublicationCommunityUrlByInterHash(String interHash) {
		final String url = this.projectHome + this.PUBLICATION_PREFIX
				+ "/" + interHash;
		return this.getUrl(url);
	}

	/**
	 * Constructs a URL for the relevant posts for a group.
	 * 
	 * @param groupName
	 * @return URL pointing to the page with posts relevant for the group with
	 *         name groupName.
	 */
	public String getRelevantForUrlByGroupName(final String groupName) {
		String url = this.projectHome + RELEVANTFOR_PREFIX + "/" + GROUP_PREFIX;
		url += "/" + UrlUtils.encodePathSegment(groupName);
		return this.getUrl(url);
	}
	
	/**
	 * Constructs a search URL for the requested search string.
	 * 
	 * @param toSearch
	 * @param searchScope the search type such as 'group', 'search', 'sharedResourceSearch'
	 * @param order
	 * @return URL pointing to the results of the search.
	 */
	public String getSearchUrl(final String toSearch, SearchType searchScope, Order order) {
		UrlBuilder ub = new UrlBuilder(this.projectHome).addPathElement(SEARCH_PREFIX).addPathElement(toSearch);
		if (searchScope != SearchType.LOCAL) {
			ub.addParameter("scope", searchScope.name());
		}
		if ((order != null) && (order != Order.RANK)) {
			ub.addParameter("order", order.name().toLowerCase());
		}
		return this.getUrl(ub.asString());
	}

	/**
	 * Constructs a search URL for the requested search string.
	 * 
	 * @param toSearch
	 * @return URL pointing to the results of the search.
	 */
	public String getSearchUrl(final String toSearch) {
		return getSearchUrl(toSearch, SearchType.LOCAL, Order.RANK);
	}

	/**
	 * Returns just the url for settings.
	 * 
	 * @return settings url
	 */
	public String getSettingsUrl() {
		final String url = this.projectHome + SETTINGS_PREFIX;
		return this.getUrl(url);
	}

	/**
	 * Returns a specific page of the settings url. TODO: Make sure that the
	 * HTTP parameters are acceptable like that (with ?).
	 * 
	 * @param selTab
	 *            the selected tab to be shown
	 * @return settings url with seltab
	 */
	public String getSettingsUrlWithSelectedTab(int selTab) {
		String url = this.getSettingsUrl() + "?selTab=" + selTab;

		return this.getUrl(url);
	}

	/**
	 * Constructs the URL for the tag's page.
	 * 
	 * @param tagName
	 * @return The URL for the tag's page.
	 */
	public String getTagUrlByTagName(final String tagName) {
		String url = this.projectHome + TAG_PREFIX;
		if (present(tagName)) {
			url += "/" + UrlUtils.encodePathSegment(tagName);
		}
		return this.getUrl(url);
	}

	/**
	 * If {@link #checkUrls} is <code>true</code>, each given string is
	 * converted into a {@link URL} (if that fails, <code>null</code> is
	 * returned). Otherwise, the given string is returned as is.
	 * 
	 * @param url
	 * @return
	 */
	private String getUrl(final String url) {
		if (this.checkUrls) {
			try {
				return new URL(url).toString();
			} catch (final MalformedURLException ex) {
				// FIXME!
				return null;
			}
		}
		return url;
	}

	/**
	 * Constructs the URL for the picture of a user.
	 * 
	 * @param userName
	 * @return The URL to the picture of the user.
	 */
	public String getUserPictureUrlByUsername(final String userName) {
		String url = this.projectHome + PICTURE_PREFIX + "/"
				+ USER_PREFIX + "/" + UrlUtils.encodePathSegment(userName);
		return this.getUrl(url);
	}

	/**
	 * Constructs the URL for the user's page.
	 * 
	 * @param user
	 * @return The URL for the user's page.
	 */
	public String getUserUrl(final User user) {
		return this.getUserUrlByUserName(user.getName());
	}

	/**
	 * Constructs the URL for the user's page.
	 * 
	 * @param userName
	 * @return The URL for the user's page.
	 */
	public String getUserUrlByUserName(final String userName) {
		final String url = this.projectHome + USER_PREFIX + "/" + UrlUtils.encodePathSegment(userName);
		return this.getUrl(url);
	}
	
	/**
	 * Constructs the URL for the user relation ajax controller
	 *
	 * @return the url to the user relation ajax controller
	 */
	public String getUserRelationEditUrl() {
		String url = this.projectHome + "ajax/"+ USER_RELATION;
		return this.getUrl(url);
	}

	/**
	 * Constructs the URL for the user's page with all posts tagged with tagName
	 * 
	 * @param userName
	 * @param tagName
	 * @return The URL for the user's page with all posts tagged with tagName
	 */
	public String getUserUrlByUserNameAndTagName(final String userName, final String tagName) {
		String url = this.projectHome
				+ USER_PREFIX
				+ "/"
				+ UrlUtils.encodePathSegment(userName)
				+ "/"
				+ UrlUtils.encodePathSegment(tagName);
		return this.getUrl(url);
	}
	
	/**
	 * Constructs the URL for the posts viewable for friends, i.e.
	 * /viewable/friends
	 * 
	 * @return URL pointing to the viewable posts for friends
	 */
	public String getViewableFriendsUrl() {
		final String url = this.getProjectHome() + VIEWABLE_PREFIX + "/" + VIEWABLE_FRIENDS_SUFFIX;
		return this.getUrl(url);
	}

	/**
	 * Constructs the URL for the posts viewable for friends tagged with
	 * tagName, i.e. /viewable/friends/TAGNAME
	 * 
	 * @param tagName
	 * 
	 * @return URL pointing to the viewable posts for friends tagged with
	 *         tagName
	 */
	public String getViewableFriendsUrlByTagName(final String tagName) {
		String url = this.getViewableFriendsUrl();
		url += "/" + UrlUtils.encodePathSegment(tagName);

		return this.getUrl(url);
	}

	/**
	 * Constructs the URL for the posts viewable for public i.e.
	 * /viewable/public
	 * 
	 * @return URL pointing to the public viewable posts
	 */
	public String getViewablePublicUrl() {
		final String url = this.getProjectHome() + VIEWABLE_PREFIX + "/"
				+ VIEWABLE_PUBLIC_SUFFIX;
		return this.getUrl(url);
	}

	/**
	 * Constructs the URL for the posts viewable for public tagged with tagName
	 * i.e. /viewable/public/TAGNAME
	 * 
	 * @param tagName
	 * 
	 * @return URL pointing to the public viewable posts tagged with tagName
	 */
	public String getViewablePublicUrlByTagName(final String tagName) {
		String url = this.getViewablePublicUrl();
		url += "/" + UrlUtils.encodePathSegment(tagName);

		return this.getUrl(url);
	}

	/**
	 * Constructs the URL for the posts viewable for private
	 * 
	 * @return URL pointing to the private viewable posts
	 */
	public String getViewablePrivateUrl() {
		final String url = this.getProjectHome() + VIEWABLE_PREFIX + "/" + VIEWABLE_PRIVATE_SUFFIX;
		return this.getUrl(url);
	}

	/**
	 * Constructs the URL for the posts viewable for private tagged with tagName
	 * i.e. /viewable/private/TAGNAME
	 * 
	 * @param tagName
	 * 
	 * @return URL pointing to the private viewable posts tagged with tagName
	 */
	public String getViewablePrivateUrlByTagName(final String tagName) {
		String url = this.getViewablePrivateUrl();
		url += "/" + UrlUtils.encodePathSegment(tagName);

		return this.getUrl(url);
	}

	/**
	 * Constructs the URL for all viewable posts of a group, i.e.
	 * /viewable/GROUPNAME
	 * 
	 * @param groupName
	 * @return the URL for all viewable posts of a group.
	 */
	public String getViewableUrlByGroupName(final String groupName) {
		String url = this.projectHome + VIEWABLE_PREFIX;
		url += "/" + UrlUtils.encodePathSegment(groupName);

		return this.getUrl(url);
	}

	/**
	 * Constructs the URL for all viewable posts of a group tagged with tagName
	 * 
	 * @param groupName
	 * @param tagname
	 * @return the URL for all viewable posts of a group tagged with tagName
	 */
	public String getViewableUrlByGroupNameAndTagName(final String groupName, final String tagname) {
		String url = this.getViewableUrlByGroupName(groupName);
		url += "/" + UrlUtils.encodePathSegment(tagname);

		return this.getUrl(url);
	}

	/**
	 * Checks if the given URL points to the given page. Useful for checking the
	 * referrer header.
	 * 
	 * @param url
	 * @param page
	 * @return <code>true</code> if the given URL points to the given page.
	 */
	public boolean matchesPage(final String url, final Page page) {
		final String pageName = page.getPath();
		final String absoluteUrl = this.getAbsoluteUrl(pageName);
		return (url != null) && url.contains(absoluteUrl);
	}

	/**
	 * Checks if the given URL points to the given resource.
	 * 
	 * @param url
	 *            - the URL that should be checked.
	 * @param userName
	 *            - the owner of the resource
	 * @param intraHash
	 *            - the intra hash of the resource
	 * @return <code>true</code> if the url points to the resource.
	 */
	public boolean matchesResourcePage(final String url, final String userName, final String intraHash) {
		if (!present(url)) {
			return false;
		}
		return url.matches(".*/(" + PUBLICATION_PREFIX + "|" + BOOKMARK_PREFIX + ")/[0-3]?" + intraHash + "/" + userName + ".*");
	}

	/**
	 * @param post
	 *            adds all misc field urls to the bibtex in this post
	 */
	public void setBibtexMiscUrls(final Post<BibTex> post) {
		post.getResource().addMiscField(BibTexUtils.ADDITIONAL_MISC_FIELD_BIBURL, this.getPublicationUrl(post.getResource(), post.getUser()));
	}

	/**
	 * @param post
	 * @return the rating url of the provided post
	 */
	public String getCommunityRatingUrl(final Post<? extends Resource> post) {
		return this.getResourceUrl(post) + DISCUSSION_ID;
	}
	
	/**
	 * @param personId
	 * @return String
	 */
	public String getPersonUrl(final String personId) {
		final UrlBuilder url = new UrlBuilder(this.projectHome + URLGenerator.PERSON_PREFIX);
		url.addPathElement(personId);
		return this.getUrl(url.asString());
	}

	/**
	 * @param authorIndex 
	 * @param resourceHash
	 * @param role
	 * @return String
	 */
	public String getDisambiguationUrl(String resourceHash, final PersonResourceRelationType role, final Integer authorIndex) {
		if (resourceHash.length() < 33) {
			resourceHash = HashID.INTER_HASH.getId() + resourceHash;
		}
		return this.getUrl(new UrlBuilder(this.projectHome + URLGenerator.DISAMBIGUATION_PREFIX)
			.addPathElement(resourceHash)
			.addPathElement(role.name().toLowerCase())
			.addPathElement(Integer.toString(authorIndex.intValue()))
			.asString());
	}
	
	public String getPersonsUrl() {
		return this.projectHome + URLGenerator.PERSON_INTRO;
	}
	
	public String getPostPublicationUrl() {
		return this.projectHome + URLGenerator.POST_PUBLICATION;
	}

	/**
	 * @param helpPage
	 * @param language
	 * @return the help page
	 */
	public String getHelpPage(final String helpPage, final String language) {
		return this.getHelpPage(null, helpPage, language);
	}

	/**
	 * @param prefixPath
	 * @param helpPage
	 * @param language
	 * @return the help page
	 */
	public String getHelpPage(final String prefixPath, final String helpPage, final String language) {
		final UrlBuilder builder = new UrlBuilder(this.projectHome + "help" + "_" + language);

		if (present(prefixPath)) {
			builder.addPathElement(prefixPath);
		}

		final String helpPath;
		// handle anchor
		if (helpPage.contains("#")) {
			final String[] pathAnchor = helpPage.split("#");
			builder.setAnchor(pathAnchor[1]);
			helpPath = pathAnchor[0];
		} else {
			helpPath = helpPage;
		}

		// check if help page is in a subdir
		if (helpPath.contains("/")) {
			final String[] path = helpPath.split("/");
			for (String pathElement : path) {
				builder.addPathElement(pathElement);
			}
		} else {
			builder.addPathElement(helpPath);
		}

		return this.getUrl(builder.asString());
	}

	/**
	 * ProjectHome defaults to <code>/</code>, such that relative URLs are
	 * generated. Note that this does not work with
	 * {@link #setCheckUrls(boolean)} set to <code>true</code>, since
	 * {@link URL} does not support relative URLs (or more correctly: relative
	 * URLs are not URLs).
	 *
	 * @param projectHome
	 */
	public void setProjectHome(final String projectHome) {
		this.projectHome = projectHome;
	}

	/**
	 * If set to <code>true</code>, all generated URLs are put into {@link URL}
	 * objects. If that fails, <code>null</code> is returned. The default is
	 * <code>false</code> such that no checking occurs.
	 *
	 * @param checkUrls
	 */
	public void setCheckUrls(final boolean checkUrls) {
		this.checkUrls = checkUrls;
	}

	/**
	 * @see URLGenerator#setCheckUrls(boolean)
	 * @return checkUrls
	 */
	public boolean isCheckUrls() {
		return this.checkUrls;
	}
}
